/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "gamewidget.h"
#include "gamesettings.h"
#include "spacecraft.h"
#include "asteroid.h"
#include "collectible.h"
#include "gameengine.h"
#include "timermanager.h"
#include "soundengine.h"
#include "visualeffectdata.h"
#include "particledata.h"
#include "ufo.h"

#include <math.h>

#include <QMouseEvent>
#include <QDebug>

/*!
 * \class GameWidget
 *
 * \brief \c GameWidget class instance renders the game screen, it also owns a \c GameEngine 
 * instance and calls the main game loop (\c GameEngine::cycleGameLoop()) in game play mode 
 * on each \c paintGL() screen refresh call.
 *
 * The \c GameWidget class extends \c QGLWidget. The game is run from 
 * \c GameAppViewer::enterGameMode() starting a timer (\c GameAppViewer::timer) which will 
 * repeatedly launch \c QGLWidget::updateGL() slot as quickly as possible.
 * \c QGLWidget::updateGL() calls \c QGLWidget::glDraw() which calls the the virtual function
 * \c paintGL() (\c GameWidget::paintGL()).

 * In addition to normal game mode SmashMiner has a special rewind mode feature. Rewind mode
 * can be enabled and disabled from game settings. When rewind mode is enabled and miner dies
 * the game goes to rewind playback mode and replays the game play during the last rewind period.
 * When in rewind playback mode the \c GameWidget::cycleRewindMode() is called instead of
 * \c GameEngine::cycleGameLoop(). The rewind mode is initialized by calling \c
 * GameWidget::enterRewindMode(). The rewind mode is finished by calling \c
 * GameWidget::leaveRewindMode().
 *
 * \c GameWidget has methods for managing the OpenGL viewport (\c GameWidget::initializeGL(), \c
 * GameWidget::resizeGL(), GameWidget::paintGL()) and for 
 * initializing (\c GameWidget::initObjects()) and painting game objects (see \c
 * GameWidget::GLes2PaintMissile() for an example).   
 *
 * \c GameWidget has also methods for switching from different program states between OpenGL 
 * and QML viewport (\c GameWidget::gameOverMode(), \c GameWidget::initNewGame(), 
 * GameWidget::switchPauseMode()).
 *
 * In addition to \c GameEngine instance \c GameWidget owns the \c SoundEngine.
 *
 * \c GameWidget has methods reading sensors (\c GameWidget::sensorReadings()) and 
 * handling user input (\c GameWidget::mousePressEvent(), \c GameWidget::mouseMoveEvent(),
 * \c GameWidget::shipModeButtonPressed(), \c GameWidget::bombButtonPressed(), 
 * \c GameWidget::hyperJumpButtonPressed()). 
 */

/*!
  Constructor of GameWidget

  \a settings Game settings.
*/
GameWidget::GameWidget(GameSettings *settings) 
    : QGLWidget()
#ifdef SMASHMINER_CONFIG_ENABLE_DESKTOP_CONTROLS
    , mouseSteeringX(0.0)
    , mouseSteeringY(0.0)
#endif  // SMASHMINER_CONFIG_DISABLE_ENABLE_CONTROLS
#ifdef SMASHMINER_CONFIG_USE_OPENGL_ES_2
    , vertexShaderSimple(0)
    , vertexShaderBackground(0)
    , vertexShaderBackgroundEffect2D(0)
    , vertexShaderConstantMaterial(0)
    , vertexShaderConstantMaterialEffect3D(0)
    , vertexShaderConstantMaterialSpecular(0)
    , vertexShaderConstantMaterialSpecularEffect3D(0)
    , vertexShaderVariableMaterial(0)
    , vertexShaderVariableMaterialEffect3D(0)
    , vertexShaderParticles(0)
    , vertexShaderParticlesEffect2D(0)
    , vertexShaderTexture(0)
    , vertexShaderTextureEffect3D(0)
    , fragmentShaderRedOnly(0)
    , fragmentShaderGreenOnly(0)
    , fragmentShaderBlueOnly(0)
    , fragmentShaderBackGround(0)
    , fragmentShaderBackGroundEffect(0)
    , fragmentShaderMaterial(0)
    , fragmentShaderParticles(0)
    , fragmentShaderTexture(0)
    , programBackground(0)
    , programBackgroundEffect2D(0)
    , programConstantMaterial(0)
    , programConstanMaterialEffect3D(0)
    , programConstantMaterialSpecular(0)
    , programConstantMaterialSpecularEffect3D(0)
    , programVariableMaterial(0)
    , programVariableMaterial3D(0)
    , programParticles(0)
    , programParticlesEffect2D(0)
    , programTexture(0)
    , programTextureEffects3D(0)
#ifdef SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES
    , programControlCauges(0)
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    , programRewindCauge(0)
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#endif  // SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES
    , backgroundImageOriginalWidth(1708)
    , backgroundImageOriginalHeight(960)
    , backgroundVertexDataCount(-1)
    , backgroundTextureCoordinateDataStartIndex(-1)
    , backgroundTextureCoordinateDataCount(-1)
    , texBombData(NULL)
    , texHyperJumpData(NULL)
    , texNumber0Data(NULL)
    , texNumber1Data(NULL)
    , texNumber2Data(NULL)
    , texNumber3Data(NULL)
    , texNumber4Data(NULL)
    , texNumber5Data(NULL)
    , texNumber6Data(NULL)
    , texNumber7Data(NULL)
    , texNumber8Data(NULL)
    , texNumber9Data(NULL)
    , texBigNumber0Data(NULL)
    , texBigNumber1Data(NULL)
    , texBigNumber2Data(NULL)
    , texBigNumber3Data(NULL)
    , texBigNumber4Data(NULL)
    , texBigNumber5Data(NULL)
    , texBigNumber6Data(NULL)
    , texBigNumber7Data(NULL)
    , texBigNumber8Data(NULL)
    , texBigNumber9Data(NULL)
    , texLetterXData(NULL)
    , texLetterColonData(NULL)
    , texMinerData(NULL)
    , texRewindRData(NULL)
    , texTimeData(NULL)
    , texGetReadyData(NULL)
    , texLevelData(NULL)
    , texLevelSmallData(NULL)
    , texGameOverData(NULL)
    , texPausedData(NULL)
    , texButtonPauseData(NULL)
    , texButtonPlayData(NULL)
    , texButtonModeBallData(NULL)
    , texButtonModeConeData(NULL)
    , texButtonBombData(NULL)
    , cometData(NULL)
#endif // SMASHMINER_CONFIG_USE_OPENGL_ES_2
    , settings(settings)
    , paintWithBlackHole(false)
    , isPaused(false)
    , isMainMenuActive(false)
    , isGameOver(false)
    , millisecondsCounter(0)
    , lefthanded(false)
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    , rewindMode(false)
    , rewindFirstFrame(-1)
    , rewindLastFrame(-1)
    , rewindCurrentFrame(-1)
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#ifdef SMASHMINER_CONFIG_ENABLE_FPS_PROFILING
    , profileFile(new QFile("/tmp/smashprofile"))
    , profileTextStream(NULL)
    , worstFrameNumber(-1)
#endif  // SMASHMINER_CONFIG_ENABLE_FPS_PROFILING
{

#ifdef SMASHMINER_CONFIG_USE_OPENGL_ES_2

    numberImages[0] = QImage(":/images/16x32_0.png");
    numberImages[1] = QImage(":/images/16x32_1.png");
    numberImages[2] = QImage(":/images/16x32_2.png");
    numberImages[3] = QImage(":/images/16x32_3.png");
    numberImages[4] = QImage(":/images/16x32_4.png");
    numberImages[5] = QImage(":/images/16x32_5.png");
    numberImages[6] = QImage(":/images/16x32_6.png");
    numberImages[7] = QImage(":/images/16x32_7.png");
    numberImages[8] = QImage(":/images/16x32_8.png");
    numberImages[9] = QImage(":/images/16x32_9.png");

#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2

    engine = new GameEngine(settings->endRewind(), settings->difficultyLevel());
    engine->setScreenSize(width(), height());

    connect(engine, SIGNAL(gameOver(int,FPtype)), this, SLOT(gameOverMode(int,FPtype)));

    connect(settings, SIGNAL(leftHandedChanged(bool)), this, SLOT(setLeftHandedness(bool)));
    connect(settings, SIGNAL(endRewindChanged(bool)), this, SLOT(setRewindEnabled(bool)));

#ifdef SMASHMINER_CONFIG_USE_OPENGL_ES_2
    QObject::connect(engine, SIGNAL(changeBackGroundImage(int)), this, SLOT(setBackGroundImage(int)));
#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2

    soundEngine = new SoundEngine(settings->soundLevel(), this);
    engine->setSoundEngine(soundEngine);
    connect(settings, SIGNAL(soundLevelChanged(int)), soundEngine, SLOT(setVolumeLevel(int)));

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    connect(engine, SIGNAL(playerCrashed()), this, SLOT(enterRewindMode()));
#endif

    setAttribute(Qt::WA_PaintOnScreen);
    setAttribute(Qt::WA_NoSystemBackground);
    setAutoBufferSwap(false);

#ifdef SMASHMINER_CONFIG_ENABLE_DESKTOP_CONTROLS

    // enable mouse tracking for mouse-based steering.

    setMouseTracking(true);

#endif  // SMASHMINER_CONFIG_DISABLE_ENABLE_CONTROLS

    // initialize geometry for painting the blackhole particles.

    bhParticlesXAxis[0] = +1.0f;
    bhParticlesXAxis[1] = +0.0f;
    bhParticlesXAxis[2] = +0.0f;

    bhParticlesYAxis[0] = +0.0f;
    bhParticlesYAxis[1] = +1.0f;
    bhParticlesYAxis[2] = +0.0f;

    GLfloat tmpZ[3] = { +0.0f, +0.0f, +1.0f };
    computeNormalForVectors(bhParticlesXAxis, bhParticlesYAxis, tmpZ);
    computeNormalForVectors(bhParticlesYAxis, tmpZ, bhParticlesXAxis);

    setLeftHandedness(settings->leftHanded());

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    rewindCurrentFrameInfo = engine->frameInfoQ.end();

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

#ifdef SMASHMINER_CONFIG_ENABLE_FPS_PROFILING

    if (profileFile->open(QFile::WriteOnly | QFile::Truncate))
        profileTextStream = new QTextStream(profileFile);
    worstFrameStats.reset();

#endif  // SMASHMINER_CONFIG_ENABLE_FPS_PROFILING
}

/// Destructor of GameWidget
GameWidget::~GameWidget()
{
    delete engine;

#ifndef SMASHMINER_CONFIG_USE_OPENGL_ES_2

    GLCleanup();

#else   // SMASHMINER_CONFIG_USE_OPENGL_ES_2

    GLes2Cleanup();

    delete texBombData;
    delete texHyperJumpData;
    delete texNumber0Data;
    delete texNumber1Data;
    delete texNumber2Data;
    delete texNumber3Data;
    delete texNumber4Data;
    delete texNumber5Data;
    delete texNumber6Data;
    delete texNumber7Data;
    delete texNumber8Data;
    delete texNumber9Data;
    delete texBigNumber0Data;
    delete texBigNumber1Data;
    delete texBigNumber2Data;
    delete texBigNumber3Data;
    delete texBigNumber4Data;
    delete texBigNumber5Data;
    delete texBigNumber6Data;
    delete texBigNumber7Data;
    delete texBigNumber8Data;
    delete texBigNumber9Data;
    delete texLetterXData;
    delete texLetterColonData;
    delete texMinerData;
    delete texRewindRData;
    delete texTimeData;
    delete texGetReadyData;
    delete texLevelData;
    delete texLevelSmallData;
    delete texGameOverData;
    delete texPausedData;
    delete texButtonPauseData;
    delete texButtonPlayData;
    delete texButtonModeBallData;
    delete texButtonModeConeData;
    delete texButtonBombData;
    delete cometData;

#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2

#ifdef SMASHMINER_CONFIG_ENABLE_FPS_PROFILING
    if (profileTextStream != NULL)
        profileTextStream->flush();
    profileFile->close();
    if (profileTextStream != NULL)
        delete profileTextStream;
    delete profileFile;
#endif  // SMASHMINER_CONFIG_ENABLE_FPS_PROFILING
}

/// Gets the sound engine
SoundEngine * GameWidget::getSoundEngine()
{
    return soundEngine;
}

/// Enter to the game over mode
void GameWidget::gameOverMode(int score, FPtype gameTime)
{
    isGameOver = true;
    emit gameOver(score, gameTime);
}

/*!
    Initializes game time measurement.
    This method should be always called before
    starting (also after a pause) the game loop.
*/
void GameWidget::initTimeMeasurement()
{
    // this method should be always called before
    // starting (also after a pause) the game loop.

    // we fill the msecs buffer with "guessed" values,
    // so that the game loop may be reasonably started.
    // real time measurements will soon fill the buffer.

    for (int i = 0;i < MSECS_BUFF_SIZE;i++)
        millisecondsArray[i] = MSECS_BUFF_DEFAULT_VALUE;

    millisecondsCounter = 0;
    time.start();
}

///  Initializes new game.
void GameWidget::initNewGame()
{
    isGameOver = false;
    engine->initNewGame(settings->endRewind(), settings->difficultyLevel());
}

/// Resizes the widget to given \a width and \a height.
void GameWidget::resizeGL(int width, int height)
{
#if defined(Q_WS_MAEMO_5)

    // when starting the game mode first time,
    // an extra resize event (640x480) is received
    // before the final event (800x480).

    // -> filter the extra event away to avoid flicker.

    if (width < 700)
        return;

#endif  // defined(Q_WS_MAEMO_5)

    if (engine != NULL) {
        const int oldWidth = engine->screenWidth();
        const int oldHeight = engine->screenHeight();

        engine->setScreenSize(width, height);

#ifndef SMASHMINER_CONFIG_USE_OPENGL_ES_2

        GLSetupViewPort();
        GLInitBackGround();

#else   // SMASHMINER_CONFIG_USE_OPENGL_ES_2

        GLes2SetupViewPort();
        GLes2InitBackGround();

#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2

        engine->doResizeRecovery(oldWidth, oldHeight);
    }
}

/// Initializes OpenGL
void GameWidget::initializeGL()
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    if (engine == NULL)
        qFatal("GameWidget::initializeGL() : engine == NULL");
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // do the necessary opengl initialization
    // tasks and settings here.

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);   // black

#ifndef SMASHMINER_CONFIG_USE_OPENGL_ES_2

    GLInitialize();

#else   // SMASHMINER_CONFIG_USE_OPENGL_ES_2

    GLes2Initialize();

#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2
}

/*!
    Sets the game view ranges in respect to current screen width and height.

    For this game the default screen mode is landscape (width > height).
    First set the GameEngine::gameViewRangeX to #VIEW_RANGE_XY and scale
    \c GameEngine::gameViewRangeY according to the screen dimensions (\c GameEngine::screenWidth()
    and \c GameEngine::screenHeight()) - then scale BOTH of them
    so that their average is set to value #VIEW_RANGE_XY.

    This should be called whenever the screen dimensions change.
*/
void GameWidget::setViewRanges()
{
    engine->gameViewRangeX = VIEW_RANGE_XY;
    engine->gameViewRangeY = engine->gameViewRangeX * (FPtype) engine->screenHeight() 
        / (FPtype) engine->screenWidth();

    const GLfloat scaling = 2.0f * VIEW_RANGE_XY 
        / (engine->gameViewRangeX + engine->gameViewRangeY);

    engine->gameViewRangeX *= scaling;
    engine->gameViewRangeY *= scaling;
    engine->gameViewRangeZ = VIEW_RANGE_Z;
}

/// Initializes the OpenGL graphics data for game objects.
void GameWidget::initObjects()
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    if (engine == NULL) 
        qFatal("GameWidget::initObjects() : engine == NULL");
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    initSpaceShip();

    initAsteroid();

    initUfo(ufoColor1VertexData, ufoColor1NormalData,
            ufoColor2VertexData, ufoColor2NormalData,
            ufoLightsVertexData, ufoLightsNormalData);

    initMineral();

    initMissile();
}

/*!
 * \brief Renders the OpenGL game screen, calculates a time step for the current game frame and 
 * read sensors.
 *
 * The timestep is calculated by measuring how many milliseconds have passed since last
 * timer start or restart call (how many milliseconds have passed during the last game frame).
 * A "sanity check" is provided for the measured time. New value is stored to a time step buffer
 * and an average value of the values in the buffer is calculated. The average value will be passed to 
 * the main game loop (\c GameEngine::cycleGameLoop).
 *
 * If in rewind playback mode, the timestep is called from current
 * rewind \c FrameInfo data and the \c cycleRewindMode() is called for 
 * rewind frame playback.
 *
 * Calls \c sensorReadings() to read sensors for user interaction.
 *
 * If the miner is changing from cone to ball mode or vice versa, 
 * call \c setSpaceShipVertexData() to update the space ship graphics definition.
 *
 * Calls \c GLes2Paint() to render the game screen.
 *
 * Writes also profiling data to file (set in GameWidget::profileFile) if 
 * #SMASHMINER_CONFIG_ENABLE_FPS_PROFILING is set. 
 *
 * Excerpt from a profiling file:
 * \code
 * -----------------------------------
 * 1 |   2   |3|4|   5   |     6      
 * -----------------------------------
 * ...
 * 21 43.4783 1 0 m= 1100 t= 416 -1 -1
 * 22 45.4545 1 0 m= 1100 t= 438 -1 -1
 * 23 55.5556 1 0 m= 1100 t= 456 -1 -1
 * 24 58.8235 1 0 m= 1100 t= 472 -1 -1
 * 25 58.8235 1 0 m= 1100 t= 488 -1 -1
 * 26 58.8235 1 0 m= 1100 t= 504 -1 -1
 * 27 58.8235 1 0 m= 1100 t= 520 -1 -1
 * ...
 * -----------------------------------
 * \endcode
 * The columns are:
 * -# frame number
 * -# time in seconds
 * -# number of objects
 * -# number of particles
 * -# m-bit string from high bit to low bit
 *      - is miner visible
 *      - is ''Get Ready'' timer active
 *      - hyper jump out data status
 *      - hyper jump in data staus
 * -# time columns (t=...)
 *      - ''Get Ready'' timer value
 *      - ''Hyper jump out'' timer value
 *      - ''Hyper jump in'' timer value
 */
void GameWidget::paintGL()
{
    // if we are in an uninitialized state,
    // then just swapbuffers() and return.

    if (!engine || isGameOver) {
        swapBuffers();
        return;
    }

    if (isPaused) {

        // in pause mode, just paint the graphics and return.
        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    #ifndef SMASHMINER_CONFIG_USE_OPENGL_ES_2

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        GLPaint(0.0f);

    #else   // SMASHMINER_CONFIG_USE_OPENGL_ES_2

        glClear(GL_DEPTH_BUFFER_BIT);
        GLes2Paint(0.0f);

    #endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2

        swapBuffers();
        return;
    }

    // the normal game mode operation starts now.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    // do the time measurements. do a "sanity check" for measured time,
    // because if we do even a single a game loop cycle using an insane
    // timestep, we can possibly mess up the GameEngine state badly...
    //
    // See the function documentation about time measurement.

    const int MinMilliseconds = 5;
    const int MaxMilliseconds = 500;

    int milliseconds = time.restart();

    if (milliseconds < MinMilliseconds)
        milliseconds = MinMilliseconds;

    if (milliseconds > MaxMilliseconds)
        milliseconds = MaxMilliseconds;

    if (millisecondsCounter < 0 || millisecondsCounter >= MSECS_BUFF_SIZE)
        millisecondsCounter = 0;

    millisecondsArray[millisecondsCounter++] = milliseconds;

    int millisecondsSum = 0;
    for (int i = 0;i < MSECS_BUFF_SIZE;i++) millisecondsSum += millisecondsArray[i];
    const FPtype measuredTimeStep = (FPtype) millisecondsSum / (FPtype) MSECS_BUFF_SIZE;

    // read the sensor values (in normal game mode).
    // if compiled with testing mode, apply test steering commands.

#ifndef SMASHMINER_CONFIG_ENABLE_TESTINGMODE
    sensorReadings();
#else   // SMASHMINER_CONFIG_ENABLE_TESTINGMODE
    testingModeSteering(engine->totalTimeCounter);
#endif  // SMASHMINER_CONFIG_ENABLE_TESTINGMODE

    // check effect counters.
    //^^^^^^^^^^^^^^^^^^^^^^^^

    // if shipModeStatus() returns value between ]0.0 ... 1.0[
    // then update the ship graphics accordingly.

    const GLfloat shipModeStatus = engine->shipModeStatus();
    const bool transition = (shipModeStatus > 0.0f && shipModeStatus < 1.0f);

    const bool force = engine->updateSpaceShipGraphics;

    if (transition || force) {

        // the space ship is transforming from one mode to another.

        setSpaceShipVertexData(shipModeStatus);

        if (force)
            engine->updateSpaceShipGraphics = false;
    }

    // run the game logic.

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    const FPtype actualTimeStep = rewindMode 
        ? (*rewindCurrentFrameInfo)->milliSeconds() : measuredTimeStep;

    if (rewindMode)
        cycleRewindMode(actualTimeStep);
    else
        engine->cycleGameLoop(actualTimeStep);

#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    const FPtype actualTimeStep = measuredTimeStep;

    engine->cycleGameLoop(actualTimeStep);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // during GameEngine::cycleGameLoop() we might have entered
    // into a final "GameOver" state, and the OpenGL widget will
    // be hidden and the QML view will be shown. if this is true,
    // then return immediately without trying to paint any more
    // graphics since it may lead into "invalid OpenGL context"
    // type warnings.

    if (isGameOver) {
        swapBuffers();
        return;
    }

    // do the actual graphics drawing.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

#ifndef SMASHMINER_CONFIG_USE_OPENGL_ES_2

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    GLPaint(actualTimeStep);

#else   // SMASHMINER_CONFIG_USE_OPENGL_ES_2

    glClear(GL_DEPTH_BUFFER_BIT);// we can use this if the background covers the whole screen area!
    GLes2Paint(actualTimeStep);

#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2

    // write FPS and other information into a profiling file if requested.

#ifdef SMASHMINER_CONFIG_ENABLE_FPS_PROFILING

    if (engine->currentFrameStats.msecs > worstFrameStats.msecs) {
        worstFrameStats.copy(&engine->currentFrameStats);
        worstFrameNumber = engine->totalFrameCounter;
    }

    if (engine->totalFrameCounter % SMASHMINER_CONFIG_FPS_PROFILING_INTERVAL == 0) {
        if (profileTextStream != NULL) {
            const bool m0 = isObjectVisible(engine->miner);
            const bool m1 = engine->timerManager->isActive(GameTimer::stateGetReadyTimer);
            const bool m2 = (TimerManager::hyperJumpOutData != NULL && TimerManager::hyperJumpOutData->isHidden());
            const bool m3 = (TimerManager::hyperJumpInData != NULL && TimerManager::hyperJumpInData->isHidden());

            const int gr = engine->timerManager->time(GameTimer::stateGetReadyTimer);
            const int hO = engine->timerManager->time(GameTimer::hyperJumpOutTimer);
            const int hI = engine->timerManager->time(GameTimer::hyperJumpInTimer);

            (*profileTextStream) << worstFrameNumber << " ";
            (*profileTextStream) << (1000.0 / worstFrameStats.msecs) << " ";
            (*profileTextStream) << worstFrameStats.numObjects << " ";
            (*profileTextStream) << worstFrameStats.numParticles << " ";
            (*profileTextStream) << "m= " << m0 << m1 << m2 << m3 << " ";
            (*profileTextStream) << "t= " << gr << " " << hO << " " << hI << " ";
            (*profileTextStream) << endl << flush;
        }

        worstFrameStats.reset();
        worstFrameNumber = -1;
    }

#endif  // SMASHMINER_CONFIG_ENABLE_FPS_PROFILING

    swapBuffers();
}

/*! Handles the mouse press event for in-game buttons (bomb, hyper jump, pause, ship
 * mode).
 *
 * \param event Mouse event.
 */
void GameWidget::mousePressEvent(QMouseEvent *event)
{
    if (!engine)
        return;

    const int x = event->x();
    const int y = event->y();

    const int screenWidth = engine->screenWidth();
    const int screenHeight = engine->screenHeight();

    const int halfWidth = screenWidth / 2;
    const int halfHeight = screenHeight / 2;

    const int quarterWidth = halfWidth / 2;
    const int quarterHeight = halfHeight / 2;

#ifdef SMASHMINER_CONFIG_USE_OPENGL_ES_2

    // test the "go to main menu" button in pause mode.
    // enable the pause mode only in OpenGL ES 2 build where UI is shown.

    if (isPaused) {
        const bool testX = (x > screenWidth / 4 && x < 3 * screenWidth / 4);
        const bool testY = (y > screenHeight / 4 && y < 3 * screenHeight / 4);
        if (testX && testY) {
            isMainMenuActive = true;
            emit gameModeExited();
            return;
        }
    }

#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2

    // buttons at the upper left corner.
    if (x < quarterWidth && y < quarterHeight)
    {
        if (!lefthanded) {
            switchPauseMode();
        }
        else {
            if (!isPaused)
                shipModeButtonPressed();
        }
    }

    // buttons at the upper right corner.
    if (x > (quarterWidth * 3) && y < quarterHeight)
    {
        if (!lefthanded) {
            if (!isPaused)
                shipModeButtonPressed();
        }
        else {
            switchPauseMode();
        }
    }

    // if we are at pause mode, then disable rest of the buttons.
    if (isPaused)
        return;

    // buttons at the lower left corner.
    if (x < halfWidth && y > halfHeight) {
        if (lefthanded)
            bombButtonPressed();
#ifndef SMASHMINER_CONFIG_ENABLE_ACCELERATION_SENSOR
        else
            hyperJumpButtonPressed();
#endif
    }

    // buttons at the lower right corner.
    if (x > halfWidth && y > halfHeight) {
        if (!lefthanded)
            bombButtonPressed();
#ifndef SMASHMINER_CONFIG_ENABLE_ACCELERATION_SENSOR
        else
            hyperJumpButtonPressed();
#endif
    }
}

/*! Switches pause mode on and off.
 */
void GameWidget::switchPauseMode()
{
#ifdef SMASHMINER_CONFIG_USE_OPENGL_ES_2
    // enable the pause mode only in OpenGL ES 2 build where UI is shown.

    // entering and leaving the pause mode, and further the main menu,
    // must be handled in a precise order:
    // 1) enter pause mode
    // 2) leave game mode (and go to main menu)
    // 3) enter game mode (and leave the main menu)
    // 4) leave the pause mode
    // make sure that we don't get any pause button events which might
    // violate the above order (check the pause mode "main menu" button
    // code if such events are slipped through).

    if (isPaused && isMainMenuActive) 
        return; // skipping a BAD pause button event

    isPaused = !isPaused;

    if (isPaused) {
        emit pauseModeEntered();
    } else {
        emit pauseModeExited();

        // we start running the game again -> start the time counting
        // so that we won't measure the time spent on pause mode...

        time.start();
    }
#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2
}

/// Handles ship mode button press. Changes mode from cone to ball and vice versa.
void GameWidget::shipModeButtonPressed()
{
    engine->changeShipModeDelayed();
}

/// Handles bomb button press. Launches bomb at miner position.
void GameWidget::bombButtonPressed()
{
    engine->launchBomb(engine->miner->posX(), engine->miner->posY());
}

/// Handles hyper jump button press. Initiates hyper jump to miner from miner position.
void GameWidget::hyperJumpButtonPressed()
{
    bool blockHyperJump = false;

    // test that the effect is not already running.
    if (engine->hyperJumpNavigator != NULL) blockHyperJump = true;
    if (TimerManager::hyperJumpOutData != NULL) blockHyperJump = true;
    if (TimerManager::hyperJumpInData != NULL) blockHyperJump = true;

    // test that we are not in an unsuitable game state.
    if (engine->timerManager->isActive(GameTimer::stateGetReadyTimer)) blockHyperJump = true;
    if (engine->timerManager->isActive(GameTimer::stateLevelChangingTimer)) blockHyperJump = true;
    if (engine->timerManager->isActive(GameTimer::statePlayerCrashedTimer)) blockHyperJump = true;
    if (engine->timerManager->isActive(GameTimer::stateGameOverTimer)) blockHyperJump = true;

    if (!blockHyperJump && engine->hyperJumpCount > 0) {
        engine->shipHyperJumpOut();
        engine->hyperJumpCount--;
    }
}

#ifdef SMASHMINER_CONFIG_ENABLE_DESKTOP_CONTROLS
/*! Handles the mouse move event: mouse movement can be used for steering the miner 
 * instead of rotation sensor when #SMASHMINER_CONFIG_ENABLE_DESKTOP_CONTROLS is set.
 *
 * \param even  Mouse event.
 */
void GameWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (!engine)
        return;

    const FPtype tmpX = (FPtype) event->x() / (FPtype) engine->screenWidth();
    const FPtype tmpY = (FPtype) event->y() / (FPtype) engine->screenHeight();

    mouseSteeringX = (tmpX - 0.50) * 2.0;
    mouseSteeringY = (0.50 - tmpY) * 2.0;
}

#endif  // SMASHMINER_CONFIG_ENABLE_DESKTOP_CONTROLS

#define MAX_ROTATION_SENSOR_ANGLE   60.0    // this is a maximum PRACTICAL sensor reading...
/*! Gets sensor readings from acceleration sensor and rotation sensor.
 * Acceleration and rotation sensors are used for steering the miner.
 *
 * The sensor value(s) are read and scaled properly
 * for storage in \c GameEngine::steeringX and \c GameEngine::steeringY members.
 */
void GameWidget::sensorReadings()
{
    // add all possible control information into steerX
    // and steerY, then save the sum into GameEngine.

    FPtype steerX = 0.0;
    FPtype steerY = 0.0;
    FPtype shakeZ = 0.0;

#ifdef SMASHMINER_CONFIG_ENABLE_ROTATION_SENSOR

    FPtype tmpX;
    FPtype tmpY;

    const FPtype rotationSensitivityX = settings->rotationSensitivityX();
    const FPtype rotationSensitivityY = settings->rotationSensitivityY();

#if defined(Q_WS_MAEMO_5)
    // todo : is there any more precise flag for this?
    // is this related to QtMobility versioning (or Qt)?
    tmpX = rotationSensor.rotationY() / +MAX_ROTATION_SENSOR_ANGLE;     // notice that the
    tmpY = rotationSensor.rotationX() / -MAX_ROTATION_SENSOR_ANGLE;     // axes are switched...
#else
    tmpX = rotationSensor.rotationX() / +MAX_ROTATION_SENSOR_ANGLE;
    tmpY = rotationSensor.rotationY() / +MAX_ROTATION_SENSOR_ANGLE;
#endif

    steerX += tmpX * fabs(tmpX) * rotationSensitivityX;   // steering = (sensor) ^ 2
    steerY += tmpY * fabs(tmpY) * rotationSensitivityY;   // steering = (sensor) ^ 2

#endif  // SMASHMINER_CONFIG_ENABLE_ROTATION_SENSOR

#ifdef SMASHMINER_CONFIG_ENABLE_ACCELERATION_SENSOR
    // sensor data for shaking
    FPtype tmpZ;
    int accelerationSensitivityZ = settings->accelerationSensitivityZ();
    tmpZ = fabs(accelometerSensor.accelerationZ()-10);
    shakeZ = fabs(tmpZ) * accelerationSensitivityZ;
#endif  // SMASHMINER_CONFIG_ENABLE_ACCELERATION_SENSOR

#ifdef SMASHMINER_CONFIG_ENABLE_DESKTOP_CONTROLS

    // add the mouse-based steering values
    // computed and stored at mouseMoveEvent().

    tmpX = mouseSteeringX;
    tmpY = mouseSteeringY;

    steerX += tmpX * fabs(tmpX) * 1.0;      // steering = (sensor) ^ 2
    steerY += tmpY * fabs(tmpY) * 1.0;      // steering = (sensor) ^ 2

#endif  // SMASHMINER_CONFIG_ENABLE_DESKTOP_CONTROLS

    // GameEngine::setSteering() methods will check
    // the valid ranges [-1,+1] and save the values.

    engine->setSteeringX(steerX);
    engine->setSteeringY(steerY);
    // GameEngine::setShakeZ() method will check
    // the valid range [0,+100] and save the value.
    engine->setShakeZ(shakeZ);
}

/*! Gets game object vertex data by game object type (one of the enumeration
 * values in ::GameObjectType).
 */
GLfloat * GameWidget::vertexDataByType(const GameObject *gameObject)
{
    GLfloat *data = 0;
    switch (gameObject->objectType()) {
        case TypeAsteroidLarge:
            data = asteroidVertexData;
        break;
        case TypeAsteroidMediumA:
            data = asteroidMediumAVertexData;
        break;
        case TypeAsteroidMediumB:
            data = asteroidMediumBVertexData;
        break;
        case TypeAsteroidMediumC:
            data = asteroidMediumCVertexData;
        break;
        case TypeCollectibleMineralA:   // fall through
        case TypeCollectibleMineralB:   // fall through
        case TypeCollectibleMineralC:
        case TypeCollectibleMineralD:
            data = mineralVertexData;
        break;
        default:
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            qFatal("GameWidget::vertexDataByType: No data for game object type %d", 
                    gameObject->objectType());
#endif // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            break;
    };
    return data;
}

/*! Gets game object normal data by game object type (one of the enumeration
 *  values in ::GameObjectType).
 */
GLfloat * GameWidget::normalDataByType(const GameObject *gameObject)
{
    GLfloat *data = 0;
    switch (gameObject->objectType()) {
        case TypeAsteroidLarge:
            data = asteroidNormalData;
        break;
        case TypeAsteroidMediumA:
            data = asteroidMediumANormalData;
        break;
        case TypeAsteroidMediumB:
            data = asteroidMediumBNormalData;
        break;
        case TypeAsteroidMediumC:
            data = asteroidMediumCNormalData;
        break;
        case TypeCollectibleMineralA:   // fall through
        case TypeCollectibleMineralB:   // fall through
        case TypeCollectibleMineralC:
        case TypeCollectibleMineralD:
            data = mineralNormalData;
        break;
        default:
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            qFatal("GameWidget::normalDataByType: No data for game object type %d.", 
                gameObject->objectType());
#endif // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            break;
    };
    return data;
}

/*! Gets game object colour data by game object type (one of the enumeration
 *  values in ::GameObjectType).
 */
GLfloat * GameWidget::colourDataByType(const GameObject *gameObject)
{
    GLfloat *data = 0;
    switch (gameObject->objectType()) {
        case TypeAsteroidLarge:
            data = asteroidColourData;
        break;
        case TypeAsteroidMediumA:
            data = asteroidMediumAColourData;
        break;
        case TypeAsteroidMediumB:
            data = asteroidMediumBColourData;
        break;
        case TypeAsteroidMediumC:
            data = asteroidMediumCColourData;
        break;
        case TypeCollectibleMineralA:
            data = mineralAColourData;
        break;
        case TypeCollectibleMineralB:
            data = mineralBColourData;
        break;
        case TypeCollectibleMineralC:
            data = mineralCColourData;
        break;
        case TypeCollectibleMineralD:
            data = mineralDColourData;
        break;
        default:
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            qFatal("GameWidget::colourDataByType: No data for game object type %d.", 
                gameObject->objectType());
#endif // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            break;
    };
    return data;
}

/*!
    Gets game object index count by game object type (one of the enumeration
    values in ::GameObjectType).

    \a gameObject Game object

    \a indexCount The index count of game object.
*/
void GameWidget::indexCountByType(const GameObject *gameObject, int &indexCount)
{
    switch (gameObject->objectType()) {
        case TypeAsteroidLarge:
            indexCount = ASTEROID_INDICES;
            break;
        case TypeAsteroidMediumA:   // fall through
        case TypeAsteroidMediumB:   // fall through
        case TypeAsteroidMediumC:
            indexCount = ASTEROID_MEDIUM_INDICES;
            break;
        case TypeCollectibleMineralA:   // fall through
        case TypeCollectibleMineralB:   // fall through
        case TypeCollectibleMineralC:
        case TypeCollectibleMineralD:
            indexCount = MINERAL_INDICES;
            break;
        default:
#ifdef  SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            qFatal("GameWidget::indexCountByType: No data for game object type %d.", 
                gameObject->objectType());
#endif // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            break;
    };
}

/*!
 * Checks if given object is currently visible (can be rendered).
 *
 * Checks if game object is in ''alive'' state (\c GameObject::isAlive()).
 * If rewind mode playback is enabled the object state is checked
 * in the current rewind frame (\c GameObject::isAliveAtFrame()).
 *
 * \returns true if object is visible
 */
bool GameWidget::isObjectVisible(GameObject *gameObject)
{
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // if we are in the rewind mode, check whether
    // the object is alive at this frame.

    if (rewindMode)
        return gameObject->isAliveAtFrame(rewindCurrentFrame);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // in the normal game mode the object is
    // visible if it is not marked as dead.

    return gameObject->isAlive();
}

/*!
    Gets game object position by game object type.

    If rewind playback mode is enabled gets the object position in the current
    rewind frame (\c GameWidget::rewindCurrentFrame) using \c GameObject::readPosition().

    \param[in] gameObject Game object
    \param[out] position The position of game object.
*/
void GameWidget::getObjectPosition(GameObject *gameObject, GLfloat *position)
{
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // if we are in the rewind mode, fetch the position for this frame.

    if (rewindMode) {
        gameObject->readPosition(rewindCurrentFrame, position);
        return;
    }

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // fetch the current position of the object.

    position[0] = gameObject->posX();
    position[1] = gameObject->posY();
}

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
/*! Enter to the rewind mode
 *
 * Sets the rewind frame pointers (first, last and current) and initializes the game objects for 
 * rewind mode playback by calling \c GameEngine::initObjectsForRewindMode().
 */
void GameWidget::enterRewindMode()
{
    if (rewindMode) 
        qFatal("GameWidget::initRewindMode() : RewindMode is already on.");

    if (engine->frameInfoQ.size() < 1) {
        return;
    }

    // assume that GameEngine::frameInfoQ is cleaned up
    // so that the first item in the queue corresponds to
    // a frame 5 seconds in past (or less if the game has
    // not been running so long.

    rewindFirstFrame = engine->frameInfoQ.front()->frameNumber();
    rewindLastFrame = engine->frameInfoQ.back()->frameNumber();

    rewindCurrentFrame = rewindFirstFrame;
    rewindCurrentFrameInfo = engine->frameInfoQ.begin();

    rewindMode = true;

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    qDebug() << "GameWidget::enterRewindMode() called:";
    qDebug() << "first frame is " << rewindFirstFrame << " with timestamp " << engine->frameInfoQ.front()->timeStamp();
    qDebug() << "last frame is " << rewindLastFrame << " with timestamp " << engine->frameInfoQ.back()->timeStamp();

    // check index/info consistency.

    if (rewindCurrentFrame != (*rewindCurrentFrameInfo)->frameNumber())
        qFatal("GameWidget::initRewindMode() : index/info match");

    // start time measurement for rewind
    // animation time skew measurement...

    rewindClock.start();

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // finally call GameObject::initRewindMode() for each GameObject
    // and do a timer cleanup.

    engine->initObjectsForRewindMode(rewindFirstFrame);
    engine->resetTimersForRewindMode();
}

/*!
    Cycles the rewind mode loop.
    Here we need to increase the timers in GameEngine,
    so that the visual effects proceed correctly in the rewind animation.
    We do NOT process the timer handlers here in same way as in GameEngine,
    because here we just need to have the animation,
    We do NOT want to change the internal state of the GameEngine.
    We do only limited amount of processing here so that effects are displayed correctly.

    NOTICE! that we need to also make the same calls to timers (i.e. "eat away" the timer events)
    as in the game mode to prevent duplicate event processing later when the game mode is resumed.

    Here is an explicit list of things that we NOT do here:
    - Do not start a hyperjump-in effect at hyperJumpOutTimer halftime.
    - Do not change the spaceship position at hyperJumpInTimer halftime.
    - Do not apply the bomb destruction effects during a bomb effect.

    See GameEngine::handleGameTimers() how things are done in game mode!
*/
void GameWidget::cycleRewindMode(FPtype timestep)
{
    engine->timerManager->updateTimers(timestep);

    // no need to consider game state timers during rewind playback...

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    const bool timerGetReady = (engine->timerManager->time(GameTimer::stateGetReadyTimer) >= 0);
    const bool timerLevelChanging = (engine->timerManager->time(GameTimer::stateLevelChangingTimer) >= 0);
    const bool timerPlayerCrashed = (engine->timerManager->time(GameTimer::statePlayerCrashedTimer) >= 0);
    const bool timerGameOver = (engine->timerManager->time(GameTimer::stateGameOverTimer) >= 0);

    if (timerGetReady || timerLevelChanging || timerPlayerCrashed || timerGameOver)
        qFatal("GameWidget::cycleRewindMode() : game state timer(s) running."
                "stateGetReadyTimer = %d "
                "stateLevelChangingTimer = %d "
                "statePlayerCrashedTimer = %d "
                "stateGameOverTimer = %d ",
                timerGetReady,
                timerLevelChanging,
                timerPlayerCrashed,
                timerGameOver);

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // ...so we can start straight from "other timers" section.

    if (engine->timerManager->time(GameTimer::shipSafeTimer) >= 0) {
        if (engine->timerManager->isFullTime(GameTimer::shipSafeTimer)) {
            // mark the task as processed by stopping the timer.
            engine->timerManager->stopGameTimer(GameTimer::shipSafeTimer);
        }
    }

    if (engine->timerManager->time(GameTimer::shipBall2ConeTimer) >= 0) {
        if (engine->timerManager->isActive(GameTimer::shipBall2ConeTimer)) {
            const GLfloat relTime = (GLfloat) engine->timerManager->timeRel(GameTimer::shipBall2ConeTimer);
            engine->miner->setTransformationStatus(1.0f - relTime);
        }

        if (engine->timerManager->isHalfTime(GameTimer::shipBall2ConeTimer))
            engine->miner->setMode(SpaceCraftConeMode);

        if (engine->timerManager->isFullTime(GameTimer::shipBall2ConeTimer)) {
            // mark the task as processed by stopping the timer.
            engine->timerManager->stopGameTimer(GameTimer::shipBall2ConeTimer);

            engine->setShipModeImmediate(SpaceCraftConeMode);
        }
    }

    if (engine->timerManager->time(GameTimer::shipCone2BallTimer) >= 0) {
        if (engine->timerManager->isActive(GameTimer::shipCone2BallTimer)) {
            const GLfloat relTime = (GLfloat) engine->timerManager->timeRel(GameTimer::shipCone2BallTimer);
            engine->miner->setTransformationStatus(relTime);
        }

        if (engine->timerManager->isHalfTime(GameTimer::shipCone2BallTimer))
            engine->miner->setMode(SpaceCraftBallMode);

        if (engine->timerManager->isFullTime(GameTimer::shipCone2BallTimer)) {
            // mark the task as processed by stopping the timer.
            engine->timerManager->stopGameTimer(GameTimer::shipCone2BallTimer);

            engine->setShipModeImmediate(SpaceCraftBallMode);
        }
    }

    if (engine->timerManager->time(GameTimer::hyperJumpOutTimer) >= 0) {
        if (engine->timerManager->isFirstQuarterTime(GameTimer::hyperJumpOutTimer))
            TimerManager::hyperJumpOutData->setTransforming(true);

        if (engine->timerManager->isHalfTime(GameTimer::hyperJumpOutTimer)) {
            // just eat away this timer event, but do nothing...
        }

        if (engine->timerManager->isLastQuarterTime(GameTimer::hyperJumpOutTimer)) {
            TimerManager::hyperJumpOutData->setTransforming(false);
            TimerManager::hyperJumpOutData->setHidden(true);
        }

        if (engine->timerManager->isFullTime(GameTimer::hyperJumpOutTimer)) {
            // mark the task as processed by stopping the timer.
            engine->timerManager->stopGameTimer(GameTimer::hyperJumpOutTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (TimerManager::hyperJumpOutData == NULL)
                qFatal("REWIND hyperJumpOutTimer : hyperJumpOutData == NULL");

            if (!TimerManager::hyperJumpOutData->isRewind())
                qFatal("hyperJumpOutTimer : hyperJumpOutData : rewind = false");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            // cleanup the data storage...

            delete TimerManager::hyperJumpOutData;
            TimerManager::hyperJumpOutData = NULL;
        }
    }

    if (engine->timerManager->time(GameTimer::hyperJumpInTimer) >= 0) {
        if (engine->timerManager->isFirstQuarterTime(GameTimer::hyperJumpInTimer)) {
            TimerManager::hyperJumpInData->setTransforming(true);
            TimerManager::hyperJumpInData->setHidden(false);

            // here change the shipmode as it was changed in the game mode.
            // but do NOT touch the miner position, it is read from the
            // stored position in GameObject for graphics painting.

            //engine->setShipModeImmediate(SpaceCraftBallMode);   // also see GameEngine::handleGameTimers().
        }

        if (engine->timerManager->isLastQuarterTime(GameTimer::hyperJumpInTimer))
            TimerManager::hyperJumpInData->setTransforming(false);

        if (engine->timerManager->isFullTime(GameTimer::hyperJumpInTimer)) {
            // mark the task as processed by stopping the timer.
            engine->timerManager->stopGameTimer(GameTimer::hyperJumpInTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (TimerManager::hyperJumpInData == NULL)
                qFatal("REWIND hyperJumpInTimer : hyperJumpInData == NULL");

            if (!TimerManager::hyperJumpInData->isRewind())
                qFatal("hyperJumpInTimer : hyperJumpInData : rewind = false");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            // cleanup the data storage...

            delete TimerManager::hyperJumpInData;
            TimerManager::hyperJumpInData = NULL;
        }
    }

    if (engine->timerManager->time(GameTimer::bombEffectTimer) >= 0) {

        // we may NOT call applyBombDestruction() here...

        if (engine->timerManager->isFullTime(GameTimer::bombEffectTimer)) {
            // mark the task as processed by stopping the timer.
            engine->timerManager->stopGameTimer(GameTimer::bombEffectTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (TimerManager::bombEffectData == NULL)
                qFatal("REWIND bombEffectTimer : bombEffectData == NULL");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            delete TimerManager::bombEffectData;
            TimerManager::bombEffectData = NULL;
        }
    }

    // apply the rotation(s) for GameObjects.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    const int asteroidCount = engine->asteroidList.count();
    for (int i = 0;i < asteroidCount; i++) {
        Asteroid *asteroid = engine->asteroidList[i];
        if (!asteroid->isAliveAtFrame(rewindCurrentFrame))
            continue;

        asteroid->integrateRotation(timestep);
    }

    const int collectibleCount = engine->collectibleList.count();
    for (int i = 0; i < collectibleCount; i++) {
        Collectible *collectible = engine->collectibleList[i];
        if (!collectible->isAliveAtFrame(rewindCurrentFrame))
            continue;

        if (collectible->objectType() == TypeCollectibleMineralA
                || collectible->objectType() == TypeCollectibleMineralB
                || collectible->objectType() == TypeCollectibleMineralC
                || collectible->objectType() == TypeCollectibleMineralD) {
            Mineral *mineral = static_cast<Mineral *>(collectible);
            mineral->integrateRotation(timestep);
        }
    }

    const int enemyCount = engine->enemyList.count();
    for (int i = 0; i < enemyCount; i++) {
        GameObject *gameObject = engine->enemyList[i];
        if (!gameObject->isAliveAtFrame(rewindCurrentFrame))
            continue;

        if (gameObject->objectType() == TypeUfo) {
            Ufo *ufo = static_cast<Ufo *>(gameObject);
            ufo->integrateRotation(timestep);
        }
    }

    const int frame = (*rewindCurrentFrameInfo)->frameNumber();

    // Launch and advance GameEffect objects for this frame.

    while(engine->pastEffects.count() > 0 && engine->pastEffects.head()->frameNumber() <= frame) {
        GameEffect *gameEffect = engine->pastEffects.dequeue();

        if (gameEffect->frameNumber() == frame) {

        /*  qDebug() << "GameWidget::cycleRewindMode() : starting effect of type "
                     << gameEffect->type() << " at frame " << frame << "."; */

            gameEffect->start();

#ifdef  SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            // mark the origin of hyperJumpInData and hyperJumpOutData
            // as rewind-mode related (just a debugging purpose).

            if (gameEffect->type() == GameEffect::HyperJumpInEffect) {
                if (!TimerManager::hyperJumpInData)
                    qFatal("GameWidget::cycleRewindMode() : no hyperJumpInData created.");

                TimerManager::hyperJumpInData->setRewind();
            }

            if (gameEffect->type() == GameEffect::HyperJumpOutEffect) {
                if (!TimerManager::hyperJumpOutData)
                    qFatal("GameWidget::cycleRewindMode() : no hyperJumpOutData created.");

                TimerManager::hyperJumpOutData->setRewind();
            }

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

        }
        else {

            // now we got an effect which is outside the exact rewing period,
            // but within a time defined in SMASHMINER_CONFIG_LONGEST_EFFECT_PERIOD
            // past compared to rewind period starting moment.

            // the effect might be still visible in the rewind animation, and if
            // we don't start it here, we won't see it.

            // start these delayed effects in case-by-case basis, if they markedly
            // affect to the rewind animation.

            // NOTE : if a GameTimer is started in a delayed fashion like in here,
            // it will still receive all all it's quarter/half/full time events
            // correctly whenever they are queried (so the timer-based animations
            // will work correctly and catch up any lost time in the next frame).

            const GLfloat delaySeconds = (*rewindCurrentFrameInfo)->timeStamp() - gameEffect->timeStamp();

            if (delaySeconds >= 0.0f) {
                const int delayMilliSecs = delaySeconds * 1000.0f;

                if (gameEffect->type() == GameEffect::HyperJumpInEffect) {
                    if (delayMilliSecs < engine->timerManager->timerLength(GameTimer::hyperJumpInTimer)) {
                        qDebug() << "GameWidget::cycleRewindMode() : delayed HyperJumpIn start : " << delayMilliSecs;

                        gameEffect->start(delayMilliSecs);

#ifdef  SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

                        // mark the origin of hyperJumpInData
                        // as rewind-mode related (just a debugging purpose).

                        if (!TimerManager::hyperJumpInData)
                            qFatal("GameWidget::cycleRewindMode() : no hyperJumpInData created (delayed).");

                        TimerManager::hyperJumpInData->setRewind();

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                    }
                }

                if (gameEffect->type() == GameEffect::HyperJumpOutEffect) {
                    if (delayMilliSecs < engine->timerManager->timerLength(GameTimer::hyperJumpOutTimer)) {
                        qDebug() << "GameWidget::cycleRewindMode() : delayed HyperJumpOut start : " << delayMilliSecs;

                        gameEffect->start(delayMilliSecs);

#ifdef  SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

                        // mark the origin of hyperJumpOutData
                        // as rewind-mode related (just a debugging purpose).

                        if (!TimerManager::hyperJumpOutData)
                            qFatal("GameWidget::cycleRewindMode() no hyperJumpOutData created (delayed).");

                        TimerManager::hyperJumpOutData->setRewind();

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                    }
                }
            }
            else {
                qDebug() << "GameWidget::cycleRewindMode() : SKIPPED effect of type "
                         << gameEffect->type() << " fn= " << gameEffect->frameNumber()
                         << " at frame " << frame << ".";
            }
        }

        // note : if effect age is inside "LONGEST_EFFECT_PERIOD",
        // then it possibly overlaps with the NEXT rewind animation
        // if it is requested IMMEDIATELY after this one; in this case
        // we should keep this effect. however, this is a very rare
        // condition and it only can affect HyperJumpIn and HyperJumpOut
        // effects. currently this condition is ignored.

        delete gameEffect;
    }

    // cycle the rewind frame counter, and exit
    // rewind mode when all frames have been shown.

    if (++rewindCurrentFrame > rewindLastFrame) {
        leaveRewindMode();
        return;
    }

    rewindCurrentFrameInfo++;

#ifdef  SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (rewindCurrentFrame != (*rewindCurrentFrameInfo)->frameNumber())
        qFatal("GameWidget::cycleRewindMode() : index/info mismatch.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
}

/// Leave from rewind mode
void GameWidget::leaveRewindMode()
{
#ifdef  SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // this code measures the time difference between the
    // actual game and the corresponding rewind animation.

/*  const GLfloat time1 = engine->frameInfoQ.back()->timeStamp() - engine->frameInfoQ.front()->timeStamp();
    const GLfloat time2 = (GLfloat) rewindClock.elapsed() / 1000.0;

    const GLfloat skew = time2 - time1; */

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    engine->cleanUpRewindFrames(false);

    rewindFirstFrame = -1;
    rewindLastFrame = -1;

    rewindCurrentFrame = -1;
    rewindCurrentFrameInfo = engine->frameInfoQ.end();

    rewindMode = false;

    // make sure that the timers are OK for
    // continuing the game...

    engine->resetTimersForRewindMode();

    // now the rewind mode is exited, and we go back to game mode.
    // the player has just crashed, and we need to bring a new ship
    // into the screen, if there are any crafts left.

    if (engine->minersLeft < 1) // go to either GetReady or GameOver state.
        engine->timerManager->startGameTimer(GameTimer::stateGameOverTimer);
    else
        engine->timerManager->startGameTimer(GameTimer::stateGetReadyTimer);
}

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

/// Sets left handedness mode
void GameWidget::setLeftHandedness(bool leftHanded)
{
    lefthanded = leftHanded;
}

/*! Sets the rewind mode state.
 *
 * \param rewindEnabled Sets rewind mode enabled (true) or disabled (false).
 */
void GameWidget::setRewindEnabled(bool rewindEnabled)
{
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // if we are asked to disable rewind while we are
    // showing a rewind animation, then exit and
    // cleanup the rewind animation state.

    if (!rewindEnabled && rewindMode)
        leaveRewindMode();

    // apply the new settings to GameEngine.

    engine->setRewindEnabled(rewindEnabled);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
}
